﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lucene.Net.Index;
using System.Threading;
using Lucandra.Net.Connection;
using Lucandra.Net.Connection.Pooling;

namespace Lucandra.Net
{
    internal sealed class LucandraSearchContext : IDisposable
    {
        #region Static Implementations

        private static ThreadLocal<LucandraSearchContext> searchContexts;

        static LucandraSearchContext()
        {
            searchContexts = new ThreadLocal<LucandraSearchContext>();
        }

        public static LucandraSearchContext Current
        {
            get { return searchContexts.Value; }
            set { searchContexts.Value = value; }
        }

        #endregion

        #region Fields

        private object lockObject = new object();
        private Dictionary<Term, int> termDocFrequencies;
        private LucandraReader reader;
        private Dictionary<Term, LucandraTermInfo> termInfoCache;

        #endregion

        #region Properties [public]

        public CassandraClient Client { get; private set; }
        public bool IsDisposed { get; private set; }

        #endregion

        #region Constructors

        public LucandraSearchContext(LucandraReader reader)
        {
            this.reader = reader;
            this.termDocFrequencies = new Dictionary<Term, int>();
            this.termInfoCache = new Dictionary<Term, LucandraTermInfo>();

            // Borrow a cassandra client connection from the pool.  If the keyspace of the reader does not match the
            // set keyspace 
            this.InvalidateClient();
        }

        #endregion

        #region Methods [public]

        public void CacheTermInfo(LucandraTermInfo termInfo)
        {
            this.termInfoCache[termInfo.GetTerm()] = termInfo;
        }

        public int GetDocumentFrequency(Term term)
        {
            int freq;
            if (!this.termDocFrequencies.TryGetValue(term, out freq))
            {
                freq = this.reader.DocFreq(term);
                termDocFrequencies[term] = freq;
            }

            return freq;
        }

        public LucandraTermInfo GetTermInfo(Term term)
        {
            LucandraTermInfo termEnum = null;
            this.termInfoCache.TryGetValue(term, out termEnum);
            return termEnum;
        }

        public CachedTermDocs GetTermDocs(Term term)
        {
            var termInfo = this.GetTermInfo(term);

            return termInfo == null
                ? null
                : new CachedTermDocs(this.reader, termInfo);
        }

        public CassandraClient InvalidateClient()
        {
            try
            {
                this.Client = CassandraConnectionPool.Borrow();
                if (this.Client.Keyspace != reader.Keyspace)
                    this.Client.SetKeyspace(reader.Keyspace);
            }
            catch (CassandraClientInvalidatedException)
            {
                this.InvalidateClient();
            }

            return this.Client;
        }

        #endregion

        /// <summary>
        /// Using finalizer over IDisposable to ensure that the CassandraClient gets returned to the pool.
        /// </summary>
        ~LucandraSearchContext()
        {
            lock (this.lockObject)
            {
                if (this.IsDisposed)
                    return;

                if (this.Client != null)
                    this.Client.Dispose();
                this.Client = null;

                this.termDocFrequencies.Clear();
                this.termInfoCache.Clear();

                this.IsDisposed = true;
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            lock (this.lockObject)
            {
                if (this.IsDisposed)
                    return;

                if (this.Client != null)
                    this.Client.Dispose();
                this.Client = null;

                this.termDocFrequencies.Clear();
                this.termInfoCache.Clear();

                this.IsDisposed = true;
            }
        }

        #endregion
    }
}
